
#include "cybertron/core/UtilPath.hpp"
#include "cybertron/core/UtilString.hpp"
#include "../PlatformHeader.hpp"
#include <codecvt>
#include <locale>
#if CYBERTRON_LINUX || CYBERTRON_MAC || CYBERTRON_IOS
  #include <unistd.h>
  #include <sys/types.h>
  #include <dirent.h>
  #include <string.h>
#endif

#if CYBERTRON_WIN
  #include <windows.h>
#endif

CYBERTRON_BEGIN

CybertronCoreAPI std::string UtilPath::getExecutableFile()
{
#ifdef CYBERTRON_WIN
	wchar_t buffer[MAX_PATH];
	GetModuleFileNameW(NULL, buffer, MAX_PATH);
	std::wstring bufferW = buffer;
	return UtilString::w2u(bufferW);

#else
    // FIXME: /proc/self/cmdline return relative path
	char buff[FILENAME_MAX];
	FILE* stream = fopen("/proc/self/cmdline", "r");
	fgets(buff, FILENAME_MAX, stream);
	fclose(stream);
	std::string buffer(buff);
	return buffer;
#endif
}

CybertronCoreAPI std::string UtilPath::getExecutablePath()
{
    // TODO: should use getDir(getExecutableFile)
#ifdef CYBERTRON_WIN
	wchar_t buffer[MAX_PATH];
	GetModuleFileNameW(NULL, buffer, MAX_PATH);
	std::wstring bufferW = buffer;
	std::wstring::size_type pos = bufferW.find_last_of(L"\\/");
	bufferW = bufferW.substr(0, pos);
	return UtilString::w2u(bufferW);
	
#else

    char buff[FILENAME_MAX] = { 0 };
    int res = readlink("/proc/self/exe", buff, sizeof(buff));
    return res < 0 ? std::string() : getDir(std::string(buff));
#endif
}

CybertronCoreAPI std::string UtilPath::getWorkingPath()
{
#ifdef CYBERTRON_WIN
	wchar_t buffer[MAX_PATH];
	//char buffer[MAX_PATH] = {0};
	GetCurrentDirectoryW(MAX_PATH, buffer);
	std::wstring bufferW = buffer;
	//return buffer;
	return UtilString::w2u(bufferW);
#else
	char buff[FILENAME_MAX];
	getcwd(buff, FILENAME_MAX);
	return std::string(buff);
#endif
}

CybertronCoreAPI bool UtilPath::setWorkingPath(const std::string& path)
{
#ifdef CYBERTRON_WIN
	std::wstring pathW = UtilString::u2w(path);
	return (SetCurrentDirectoryW(pathW.c_str()) != FALSE);
#else
	return (chdir(path.c_str()) == 0);
#endif
}

CybertronCoreAPI std::string UtilPath::combine(const std::string& rootPath, const std::string& subPath)
{
	if (rootPath == "")
	{
		return subPath;
	}
	if (subPath.size() > 0)
	{
		auto firstChar = subPath[0];
		if ((firstChar == '/') || (firstChar == '\\'))
		{
			return subPath;
		}
	}
	if (subPath.find_first_of(':') != std::string::npos)
	{
		return subPath;
	}
	if (rootPath.size() > 0)
	{
		auto lastChar = rootPath[rootPath.size() - 1];
		if ((lastChar == ':') || (lastChar == '/') || (lastChar == '\\'))
		{
#ifdef CYBERTRON_WIN
			if ((lastChar == ':') && (rootPath[0] != '/'))
			{
				return rootPath + '\\' + subPath;
			}
#endif
			return rootPath + subPath;
		}
		if ((rootPath[0] == '/'))
		{
			return rootPath + '/' + subPath;
		}
	}
#ifdef CYBERTRON_WIN
	return rootPath + '\\' + subPath;
#else
	return rootPath + '/' + subPath;
#endif
}

CybertronCoreAPI std::string UtilPath::getDir(const std::string& path)
{
	auto directory_end = path.find_last_of(":/\\");
	if (directory_end == std::string::npos)
	{
		directory_end = 0;
	}
	else
	{
		directory_end += 1;
	}
	return path.substr(0, directory_end);
}

CybertronCoreAPI void UtilPath::getFilesInDir(const std::string& path,
	const std::string& fileExt,
	std::vector<std::string>& files)
{
#if CYBERTRON_WIN
	std::string pattern(path);
	pattern.append("\\*");
	if (!fileExt.empty()) {
		pattern.append(".").append(fileExt);
	}
	WIN32_FIND_DATA data;
	HANDLE hFind;
	if ((hFind = FindFirstFile(cybertron::UtilString::u2w(pattern).c_str(), &data)) != INVALID_HANDLE_VALUE) {
		do {
			files.push_back(cybertron::UtilString::w2u(data.cFileName));
		} while (FindNextFile(hFind, &data) != 0);
		FindClose(hFind);
	}
#else
	DIR* dirp = opendir(path.c_str());
	struct dirent * dp;
	while ((dp = readdir(dirp)) != NULL) {
		if (strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) {
            files.push_back(dp->d_name);
        }
	}
	closedir(dirp);
#endif
}


CybertronCoreAPI std::string UtilPath::normalizePath(const std::string& path)
{
	std::string result = path;
	for (size_t i = 0; i < result.size(); ++i)
	{
		if (result[i] == '\\')
		{
			result[i] = '/';
		}
	}

	return result;
}

CybertronCoreAPI std::string UtilPath::appendTailSlash(const std::string& path)
{
	std::string result = normalizePath(path);
	if (result.size() > 0)
	{
		if (result[result.size() - 1] != '/')
		{
			result.push_back('/');
		}
	}

	return result;
}

CybertronCoreAPI size_t UtilPath::findLastSlashPosition(const std::string& filename)
{
	if (filename.size() == 0)
	{
		return (size_t)-1;
	}

	size_t lastSlash = (size_t)-1;
	for (size_t i = filename.size(); i > 0; --i)
	{
		if (filename[i - 1] == '/' ||
			filename[i - 1] == '\\')
		{
			lastSlash = i - 1;
			break;
		}
	}

	return lastSlash;
}

CybertronCoreAPI std::string UtilPath::getPathWithoutFilename(const std::string& filename)
{
	size_t lastSlash = findLastSlashPosition(filename);
	if (lastSlash == (size_t)-1)
	{
		return std::string();
	}

	return filename.substr(0, lastSlash);
}

CybertronCoreAPI std::string UtilPath::getFilenameWithoutPath(const std::string& filename)
{
	size_t lastSlash = findLastSlashPosition(filename);
	if (lastSlash == (size_t)-1)
	{
		return filename;
	}

	return filename.substr(lastSlash + 1);
}

CybertronCoreAPI std::string UtilPath::getBasenameWithoutPath(const std::string& filename)
{
	std::string baseName = filename;
	size_t lastSlash = baseName.rfind('/');
	if (lastSlash != (size_t)-1)
	{
		baseName = baseName.substr(lastSlash + 1);
	}
	lastSlash = baseName.rfind('\\');
	if (lastSlash != (size_t)-1)
	{
		baseName = baseName.substr(lastSlash + 1);
	}
	size_t lastDot = baseName.rfind('.');
	if (lastDot != (size_t)-1)
	{
		baseName = baseName.substr(0, lastDot);
	}

	return baseName;
}

CybertronCoreAPI std::string UtilPath::getPathWithoutFileExtension(const std::string& filename)
{
	size_t lastSlash = findLastSlashPosition(filename);
	if (lastSlash == (size_t)-1)
	{
		lastSlash = 0;
	}

	size_t firstDot = filename.find('.', lastSlash);
	if (firstDot == (size_t)-1)
	{
		return filename;
	}

	return filename.substr(0, firstDot);
}

CybertronCoreAPI std::string UtilPath::getExtension(const std::string& path)
{
	size_t lastDot = path.rfind('.');
	if (lastDot == (size_t)-1)
	{
		return std::string();
	}
	std::string ext = path.substr(lastDot + 1, path.size() - lastDot - 1);

	return ext;
}

CybertronCoreAPI std::string UtilPath::getExtensionLower(const std::string& filename)
{
	std::string ext = getExtension(filename);
	const std::locale& lc = std::locale::classic();
	for (size_t i = 0; i < ext.size(); ++i)
	{
		ext[i] = std::tolower(ext[i], lc);
	}

	return ext;
}

CybertronCoreAPI bool UtilPath::isRelative(const std::string& path)
{
	const int pathLen = (int)(path.size());
	const bool isRooted = pathLen &&
		((path[0] == '/') ||												// Root of the current directory on Windows, root on UNIX-likes.  Also covers "\\", considering normalization replaces "\\" with "//".
		(pathLen >= 2 && (													// Check it's safe to access InPath[1]!
			((path[0] == '\\') && (path[1] == '\\'))					// Root of the current directory on Windows. Also covers "\\" for UNC or "network" paths.
			|| (path[1] == ':' && isalpha(path[0]))				// Starts with "<DriveLetter>:"
			))
		);
	return !isRooted;
}

CYBERTRON_END
